package ar.edu.unq.sandsoft.session;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.util.Bytes;
import org.xml.sax.SAXException;

import ar.edu.unq.sandsoft.cache.SandCacheImpl;
import ar.edu.unq.sandsoft.constructors.SandFileReader;
import ar.edu.unq.sandsoft.exceptions.CouldnFindAttribute;
import ar.edu.unq.sandsoft.exceptions.TableNotFoundException;
import ar.edu.unq.sandsoft.utils.ParsingHelper;
import ar.edu.unq.sandsoft.utils.ValueFetcher;

public class SandSessionImpl implements SandSession {
    private List<SandFileReader> readers;

    private SandCacheImpl cache;

    private int cacheHits;

    public int getCacheHits() {
        return cacheHits;
    }

    public SandCacheImpl getCache() {
        return cache;
    }

    public SandSessionImpl(final List<SandFileReader> readers, final SandCacheImpl cache) {
        this.readers = readers;
        this.cache = cache;
        this.cacheHits = 0;
    }

    public void save(Object object) throws CouldnFindAttribute {
        try {
            this.doSave(object);
        } catch (CouldnFindAttribute e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void doSave(Object object) throws IOException, SecurityException, ClassNotFoundException,
            NoSuchMethodException, CouldnFindAttribute, IllegalArgumentException, IllegalAccessException,
            InvocationTargetException {

        ParsingHelper helper = new ParsingHelper();

        SandFileReader reader = SandSessionFactory.getReaderByClassName(object.getClass().getName());
        String wantedClassName = helper.adaptClassName(reader.findClassName());
        HTable table = new HTable(HBaseConfiguration.create(), wantedClassName);

        Map<String, Method> getters = reader.createClassConstructor().prepareGetters();

        Put putRow = new Put(helper.toBytes(fetchKeyValue(reader, object)));

        for (String getterName : getters.keySet()) {
            Object value = getters.get(getterName).invoke(object, new Object[] {});
            putRow.add(Bytes.toBytes(getterName), Bytes.toBytes(getterName), helper.toBytes(value));
        }
        table.put(putRow);
    }

    public Object fetchKeyValue(SandFileReader reader, Object target) {
        return new ValueFetcher(reader).fetch(target);
    }

    public Object load(Object keyValue, Class<?> clazz) throws TableNotFoundException, CouldnFindAttribute {
        Object loaded = null;
        SandFileReader reader = SandSessionFactory.getReaderByClassName(clazz.getName());

        if (this.getCache().isInCache(reader, keyValue, clazz)) {
            loaded = this.getCache().findInCache(reader, keyValue, clazz);
            cacheHits++;
        } else {
            try {
                loaded = this.doLoad(reader, keyValue, clazz.getName());
                this.getCache().putInCache(loaded);
            } catch (NullPointerException e) {
                throw new TableNotFoundException(clazz.getName());
            } catch (CouldnFindAttribute e) {
                throw e;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return loaded;
    }

    private Object doLoad(SandFileReader reader, Object keyValue, String className) throws TableNotFoundException,
            CouldnFindAttribute, IOException, SecurityException, SAXException, ParserConfigurationException,
            ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException {

        ParsingHelper helper = new ParsingHelper();
        String tableName = helper.adaptClassName(className);
        Object instanceRequest;

        HTable table = new HTable(HBaseConfiguration.create(), tableName);
        Get get = new Get(helper.toBytes(keyValue));
        Result result = table.get(get);

        instanceRequest = reader.instanceClass();

        Map<String, Method> setters = reader.createClassConstructor().prepareSetters();
        for (String setterName : setters.keySet()) {
            byte[] bytedSetter = Bytes.toBytes(setterName);
            byte[] recorvered = result.getValue(bytedSetter, bytedSetter);
            Object setterValue = helper.toObject(recorvered);
            setters.get(setterName).invoke(instanceRequest, new Object[] { setterValue });
        }

        return instanceRequest;
    }

    public void setReaders(List<SandFileReader> readers) {
        this.readers = readers;
    }

    public List<SandFileReader> getReaders() {
        return readers;
    }

}
